Lab Outcomes

  1. Use contour and scatter plots to visualize three variables.
  2. Interpolate data between measured values to make heatmaps for visualizing change.

This lab is adapted from a R workshop hosted at Bigelow. For reference, that R workshop can be found on GitHub at: cathmmitchell/plottingOceanDataWithR

Today we are going to use the DaRTS_combined_data.csv file that we made in Lab 3.

Introduction

In our initial exploration of the data, we’ve looked at how to plot some variables under some limited conditions e.g. profiles for one cruise, or relationships between a couple of variables for all cruises. We could have used Excel to do this plotting. But what about if we want to visualize our data in a different way that represented the time and space components of field data or if we wanted a more complicated subset of our data? That’s where R comes in really handy.

What we’re going to work towards here is plotting oceanographic data in similar way to Ocean Data View. By the end of the tutorial, we’ll have created:

And I’ll provide you with some resources for how to recreate the above figure under different requirements e.g. for a different variable, or different cruise.

But, it’s worth pointing out here that all the methods we’ll be using can be used on any type of data - and we’ll see some examples as we go through the tutorial.

Initializing our R Session

Let’s start by setting up our R session. These are good steps to take at the start of any R session.

  1. Set the working directory:
setwd("C:/Users/cmitchell/Documents/SeaChangeSemester/2021/Labs/sea-change-semester-data-labs/Lab05/")
  1. Open a new script
  2. Save the new script e.g. Lab05.R
  3. Import the libraries we’ll use today
library(ggplot2)
library(dplyr)
library(lubridate)
  1. Import our data
DATA <- read.csv("DaRTS_combined_data.csv")

Contour and Bubble Plots

Let’s start with the following question:

How does surface chlorophyll fluorescence change at each station over the 2016 sampling season?

Surface can be defined in multiple ways and the exact choices you make will depend on your data and your situation. Here, we are going to say surface chlorophyll fluorescence is the average value over the top 1 m.

If you want to take the surface value as the average over the top 2 m (or any other depth), you will need to go back to the raw CTD data and redo the grouping and binning step we did in Lab 3. In this case, rather than rounding all the CTD data to the nearest meter, you’d filter for your specified depth. The R Workshop we hosted at Bigelow follows this approach, so check that out if you are interested.

We are going to create a couple of different figures that show surface chlorophyll fluorescence with date along the x-axis and station along the y-axis.

Another example: Say you’ve got three cultures that you’re testing four different treatments on. For each culture/treatment combination, you’re measuring cell counts at a series of time points. What you’d like to visualize/plot is the average cell counts over the first two time points for each culture and treatment.

How do the variables in these examples compare?

Field Data Lab Experiment
station treatment
date culture
depth time point

Data Manipulation

First, we are going to be plotting based on date, so we need to convert the dates to R dates (like we did in Lab 3).

DATA <- DATA %>% mutate(Date = lubridate::ymd(DATA$Date))

Our data frame contains data from 2012 - 2020, so we need to filter the data frame based on year and depth:

chldata2016 <- DATA %>% filter(year == 2016, Depth == 1)

Visualizing data using a contour plot

We can visualize this type of data with a contour plot (using geom_contour_filled()). We want to label the color bar, so in this case, our argument to the labs() function for the legend (color bar) is going to be fill.

ggplot(chldata2016,aes(x=Date,y=Station)) +
  geom_contour_filled(aes(z=Fluorescence_mg_m3)) +
  geom_point() +
  labs(fill='surface chlorophyll fluorescence (mg m^-3)')

We can reformat the x-axis dates if we wished - see details in the Formatting Dates wiki that is part of the R Workshop we did at Bigelow.

We can also change the locations of the x-ticks. How we do this depends on our data type - see some examples in the x-tick wiki (also from Bigelow’s R Workshop).

In this case, if we know what x-tick positions we want, we could specify them in a call to scale_x_date(). Recall to get a list of the cruise dates we can use unique:

unique(chldata2016$Date)
[1] "2016-09-08" "2016-09-20" "2016-10-04" "2016-10-19" "2016-11-01"

So we can use this in our call to scale_x_date():

ggplot(chldata2016,aes(x=Date,y=Station)) +
  geom_contour_filled(aes(z=Fluorescence_mg_m3)) +
  geom_point() +
  labs(fill='surface chlorophyll fluorescence (mg m^-3)') +
  scale_x_date(breaks=unique(chldata2016$Date))

Here, the dots (from geom_point) show where the measurements were made, and the contours filled in the gaps. This helps us to visualize the time and space changes in chlorophyll fluorescence.

If we think about our lab experiment example, what our figure would show is treatment on the y-axis, culture on the x-axis, and cell counts as the contours. In that case - does it make sense to have the cell counts fill the gaps between the treatment and culture? No - the treatments and cultures are separate from each other. They are not connected in this temporal or spatial way. So while the data manipulation was the same for each data set, the data visualization isn’t.

Visualizing data using a bubble plot

We are going to plot a scatter plot, where the size of each point represents the chlorophyll fluorescence. In this case, our argument to the labs() function for the legend is going to be size.

ggplot(chldata2016,aes(x=Date,y=Station)) +
  geom_point(aes(size=Fluorescence_mg_m3)) +
  labs(size='surface chlorophyll fluorescence (mg m^-3)')

Interpolating and visualizing data

The approach in previous section works well when your data are consistent in terms of the variables you want to compare. For the data we plotted above, there were five different cruises, and on each cruise, data was collected at the same four locations. We had data for every cruise and station. If we’d been missing data at one of the stations on one of the cruises, we’d just have a blank part on the plots we made. But what if we were missing a lot of data - would the above approach still be a good way to visualize our data?

To dig into this a bit further, let’s consider how chlorophyll fluorescence varies by depth at each station for one cruise.

We need to do a bit of data manipulation again to create a data frame we’ll use for plotting. In this case, we’re going to have station on the x-axis and depth on the y-axis, and we’ll consider the cruise that took place on September 8th 2016.

cruiseData <- filter(DATA, Date=="2016-09-08")

We now have a data frame that includes data for just one cruise - so let’s use the same approach as before to create a contour plot:

ggplot(cruiseData,aes(x=Station,y=Depth)) +
  geom_contour_filled(aes(z=Fluorescence_mg_m3)) +
  geom_point() +
  labs(fill='chlorophyll fluorescence (mg m^-3)') +
  scale_y_reverse() #flip y-axis

This looks good, but what this plotting function doesn’t do is interpolate data between missing data points. We know we have data at station 4 below 50 m that isn’t represented in this plot. Can we use a different function to show those data too? Yes - geom_tile().

ggplot(cruiseData,aes(x=Station,y=Depth)) +
  geom_tile(aes(fill=Fluorescence_mg_m3)) +
  labs(fill='chlorophyll fluorescence (mg m^-3)') +
  scale_y_reverse()

Let’s go back to the lab experiment example: could it be plotted in this way?

Yes - depth could represent time points, and station could represent treatment.

Interpolation

With our field data one axis has been station number i.e. it is a given location. We might want to space out the data on the x-axis based on those locations, rather than station number, so we can visualize our data with a representative separation between the data points. For this data set, the stations are on a similar longitude, so looking at latitude will give a good representation of the separation between the stations. For cruise or sampling tracks where both latitude and longitude are variable, this could be distance along the cruise path or field transect.

Our dataframe doesn’t have the latitude in it, only the station numbers. So we need to first redo include latitude in our data frame. To do this we are going to use conditional statements.

Coding technique: Conditional Statements

A conditional statement is one that tells you to do something based on a condition e.g. if there is cake in the supermarket, then buy 1. Sometimes, there might be a different action to take if the condition is false e.g. if there is cake in the supermarket, then buy 1, else buy 1 loaf of bread.

Here, the important words are if, then and else. Together, these make up something called an if-else statement. Sometimes it easier to visualize these as flow charts:

If we didn’t have the else statement, then, the default is to do nothing if the first condition is false.

There’s a couple of different ways to code if and if-else statements in R. To code this in R, we would do the following:

if(condition){
  code if TRUE
} else {
  code if FALSE
}

But we could also write this as the following:

ifelse(condition, code if true, code if false)

How can we use this idea for including the latitudes into our data frame? We can think of this process as:

  • if station = 1, then latitude = 43.903
  • if station = 2, then latitude = 43.863
  • if station = 3, then latitude = 43.811
  • if station = 4, then latitude = 43.753.

That’s a lot of statements. Let’s try and visualize this:

What do we do if our test for station 1 is false (i.e. if we are at station 2, 3 or 4)? We could test if it is station 2. Then, if it is, assign the station 2 latitude value, else test if it is station 3. Then, if it is station 3, we could assign the station 3 latitude value, else test if it is station 4. Then, if it is station 4, we could assign the station 4 latitude value. What do we do if the final test (i.e. station 4) is false? That means it isn’t ANY of the stations, so there’s probably an error somewhere in our dataset, and we should set that latitude to Not-A-Number (NaN).

Let’s first look at this visually:

How do we combine all these statements into some R code? We can use the ifelse function. Recall it has the form:

ifelse(condition, code if true, code if false)

In our case, we have to include an ifelse statement as the code if false part:

ifelse(station1test, latitude = value1, ifelse(station2test, latitude = value2, code if false))

And keep doing this for all the stations:

ifelse(station1test, latitude = value1, 
ifelse(station2test, latitude = value2, 
ifelse(station3test, latitude = value3,
ifelse(station4test, latitude = value4,NaN))))

Let’s just split this up to see what’s going on more clearly. We’ll start with the last ifelse:

In the above image, the part in the solid grey box is the code if false part for the last ifelse statement. For the 3rd ifelse:

The part in the dashed green box is the code if false part for the 3rd ifelse statement. For the 2nd ifelse:

The part in the dotted orange box is the code if false part for the 2nd ifelse statement. For the 1st ifelse:

The part in the solid blue box is the code if false part for the 1st ifelse.

And finally, putting this all together:

latitude = ifelse(cruiseData$Station == 1, 43.903,ifelse(cruiseData$Station == 2, 43.863,ifelse(cruiseData$Station == 3, 43.811,ifelse(cruiseData$Station == 4,43.753,NaN))))

Now let’s add this column of latitudes to our dataframe and make our plot:

cruiseData <- cruiseData %>% mutate(latitude = latitude)

ggplot(cruiseData,aes(x=latitude,y=Depth)) +
  geom_tile(aes(fill=Fluorescence_mg_m3)) +
  labs(fill='chlorophyll fluorescence (mg m^-3)') +
  scale_y_reverse() +
  scale_x_reverse() # flipping so station 1 is on the left

What we end up with is this plot where we have gaps between each station measurement because our sampling stations are not equally spaced in terms of latitude. What we can do is estimate what the chlorophyll fluorescence would be in between where we have our samples i.e. we can interpolate our data.

We are going to do the bilinear interpolation on our data, which means we are going to estimate chlorophyll fluorescence values over regularly spaced latitude and depth values. This is something that’s maybe more common when working with geospatial data. But in our lab experiment case, maybe we’d be interested in interpolating our cell counts between our different time points.

We’re going to use a handy function from the akima package to do all the hard work for us. But first, we need to make sure we have no nans in our data. Recall we did this last day.

# removing the NaNs and Infs:
# 1) make a boolean list of the good values
nonans <- is.finite(cruiseData$Fluorescence_mg_m3)
# 2) index fluorescence, latitude and depth columns using our boolean list
latnonan <- cruiseData$latitude[nonans]
flnonan <- cruiseData$Fluorescence_mg_m3[nonans]
depthnonan <- cruiseData$Depth[nonans]

Next we can do the interpolation

library(akima)

#interpolate our data:
interpReference <- interp(latnonan, depthnonan, flnonan)

What does interpReference look like? It is a list with 3 items:

  1. x, which is a list of 40, regularly spaced latitudes
  2. y, which is a list of 40, regularly spaced depths
  3. z, which is a 40 x 40 grid (or matrix), where each element (number in the grid) corresponds to one of the depth (columns) and latitude (rows) values. For example,
    1. the z value in the first row and first column corresponds to the first latitude and depth values
    2. the z value in the first row and second column corresponds to the first latitude, and second depth value
    3. the z value in the second row and first column corresponds to the second latitude, and first depth value

Here’s an example for a smaller matrix:

What we now need to do is get our data into a data frame format for ggplot. One way to do this is use the expand.grid function to create a data frame from all combinations of the latitude and depth vectors. Then flatten the interpolated data matrix into a vector and add it as a column in the data frame.

# making data frame from all combinations of latitude and depth
CruiseDataInterp <- expand.grid(latitude=interpReference$x,
                                depth = interpReference$y) %>%
  mutate(fluorescence = as.vector(interpReference$z))

Note: Something to be aware of with the above step is if the matrix is flattened row-wise or column-wise. To make sure the flattened matrix elements align with the correct latitude and depth pair, you might need to transpose your matrix first (flip it along the diagonal) t(interpReference$z)

Now we have our data frame - can we plot our data as before?

ggplot(CruiseDataInterp, aes(x=latitude, y=depth)) +
  geom_tile(aes(fill = fluorescence)) +
  scale_y_reverse() +
  scale_x_reverse() + #so station1 is on the left and station4 is on the right
  labs(y="Depth (m)", fill="fluorescence (mg m^-3)") +
  scale_fill_distiller(palette="Greens",direction=1)

We can include the sample locations and contour lines to make it clear where the interpolation is happening:

ggplot(CruiseDataInterp, aes(x=latitude, y=depth)) +
  geom_tile(aes(fill = fluorescence)) +
  geom_contour(aes(z = fluorescence),color="white") +
  geom_point(data = cruiseData, aes(x=latitude, y=Depth),color="black") + #adding in the measurement locations
  scale_y_reverse() +
  scale_x_reverse() + #so station1 is on the left and station4 is on the right
  labs(y="Depth (m)", fill="fluorescence (mg m^-3)") +
  scale_fill_distiller(palette="Greens",direction=1)

Note: In this case (the Damariscotta River) we interpolated below the measured depths. But those interpolations don’t make sense here because the profiles are full water column i.e. depth was limited by bathymetery.

In this example, we’ve interpolated our data over depth and latitude. But, we could have focused on one station and done depth by date instead. These techniques we’ve been learning are not only for this dataset and these specific parameters. What we’ve been learning are specific skills and techniques that can be applied to any type of data.

Next steps: Functions and For Loops

The last figure we created was for one variable, for one cruise. What if we want to look at a different variable? Or a different cruise? It would be nice to have a function to regenerate a plot quickly.

You could write a function that creates the above figure.

For loops are a way we can repeat a specific task a set number of times. For example, you could create plots for every cruise in 2016.

We don’t have time to go over functions and for loops, but see the Bigelow R Workshop tutorial for a detailed walk-through.

Sea Change Semester Data Labs GitHub

All the labs from Course 1 are on Bigelow’s GitHub so you can access them there any time.

Note if you want to view the lab scripts on GitHub, click on the .md file within each lab folder (not the .Rmd OR .html file).

LS0tDQp0aXRsZTogIlZpc3VhbGl6aW5nIGNoYW5nZTogUGxvdHRpbmcgcmVsYXRpb25zaGlwcyBiZXR3ZWVuIG11bHRpcGxlIHZhcmlhYmxlcyINCm91dHB1dDogDQogIGh0bWxfbm90ZWJvb2s6IGRlZmF1bHQNCiAgZ2l0aHViX2RvY3VtZW50OiBkZWZhdWx0DQphbHdheXNfYWxsb3dfaHRtbDogdHJ1ZQ0KLS0tDQoNCiMgTGFiIE91dGNvbWVzDQoNCjEuIFVzZSBjb250b3VyIGFuZCBzY2F0dGVyIHBsb3RzIHRvIHZpc3VhbGl6ZSB0aHJlZSB2YXJpYWJsZXMuDQoyLiBJbnRlcnBvbGF0ZSBkYXRhIGJldHdlZW4gbWVhc3VyZWQgdmFsdWVzIHRvIG1ha2UgaGVhdG1hcHMgZm9yIHZpc3VhbGl6aW5nIGNoYW5nZS4NCg0KVGhpcyBsYWIgaXMgYWRhcHRlZCBmcm9tIGEgUiB3b3Jrc2hvcCBob3N0ZWQgYXQgQmlnZWxvdy4gRm9yIHJlZmVyZW5jZSwgdGhhdCBSIHdvcmtzaG9wIGNhbiBiZSBmb3VuZCBvbiBHaXRIdWIgYXQ6IFtjYXRobW1pdGNoZWxsL3Bsb3R0aW5nT2NlYW5EYXRhV2l0aFJdKGh0dHBzOi8vZ2l0aHViLmNvbS9jYXRobW1pdGNoZWxsL3Bsb3R0aW5nT2NlYW5EYXRhV2l0aFIvYmxvYi9tYWluL3Bsb3R0aW5nT2NlYW5EYXRhV2l0aFIubWQpDQoNCg0KVG9kYXkgd2UgYXJlIGdvaW5nIHRvIHVzZSB0aGUgYERhUlRTX2NvbWJpbmVkX2RhdGEuY3N2YCBmaWxlIHRoYXQgd2UgbWFkZSBpbiBMYWIgMy4gDQoNCiMgSW50cm9kdWN0aW9uDQoNCkluIG91ciBpbml0aWFsIGV4cGxvcmF0aW9uIG9mIHRoZSBkYXRhLCB3ZSd2ZSBsb29rZWQgYXQgaG93IHRvIHBsb3Qgc29tZSB2YXJpYWJsZXMgdW5kZXIgc29tZSBsaW1pdGVkIGNvbmRpdGlvbnMgZS5nLiBwcm9maWxlcyBmb3Igb25lIGNydWlzZSwgb3IgcmVsYXRpb25zaGlwcyBiZXR3ZWVuIGEgY291cGxlIG9mIHZhcmlhYmxlcyBmb3IgYWxsIGNydWlzZXMuIFdlIGNvdWxkIGhhdmUgdXNlZCBFeGNlbCB0byBkbyB0aGlzIHBsb3R0aW5nLiBCdXQgd2hhdCBhYm91dCBpZiB3ZSB3YW50IHRvIHZpc3VhbGl6ZSBvdXIgZGF0YSBpbiBhIGRpZmZlcmVudCB3YXkgdGhhdCByZXByZXNlbnRlZCB0aGUgdGltZSBhbmQgc3BhY2UgY29tcG9uZW50cyBvZiBmaWVsZCBkYXRhIG9yIGlmIHdlIHdhbnRlZCBhIG1vcmUgY29tcGxpY2F0ZWQgc3Vic2V0IG9mIG91ciBkYXRhPyBUaGF0J3Mgd2hlcmUgUiBjb21lcyBpbiByZWFsbHkgaGFuZHkuIA0KDQpXaGF0IHdlJ3JlIGdvaW5nIHRvIHdvcmsgdG93YXJkcyBoZXJlIGlzIHBsb3R0aW5nIG9jZWFub2dyYXBoaWMgZGF0YSBpbiBzaW1pbGFyIHdheSB0byBbT2NlYW4gRGF0YSBWaWV3XShodHRwczovL29kdi5hd2kuZGUvKS4gQnkgdGhlIGVuZCBvZiB0aGUgdHV0b3JpYWwsIHdlJ2xsIGhhdmUgY3JlYXRlZDoNCg0KPGltZyBzcmM9ZmlndXJlcy9maW5hbF9wbG90LnBuZyB3aWR0aD03MCU+DQoNCkFuZCBJJ2xsIHByb3ZpZGUgeW91IHdpdGggc29tZSByZXNvdXJjZXMgZm9yIGhvdyB0byByZWNyZWF0ZSB0aGUgYWJvdmUgZmlndXJlIHVuZGVyIGRpZmZlcmVudCByZXF1aXJlbWVudHMgZS5nLiBmb3IgYSBkaWZmZXJlbnQgdmFyaWFibGUsIG9yIGRpZmZlcmVudCBjcnVpc2UuDQoNCkJ1dCwgaXQncyB3b3J0aCBwb2ludGluZyBvdXQgaGVyZSB0aGF0IGFsbCB0aGUgbWV0aG9kcyB3ZSdsbCBiZSB1c2luZyBjYW4gYmUgdXNlZCBvbiBhbnkgdHlwZSBvZiBkYXRhIC0gYW5kIHdlJ2xsIHNlZSBzb21lIGV4YW1wbGVzIGFzIHdlIGdvIHRocm91Z2ggdGhlIHR1dG9yaWFsLg0KDQojIEluaXRpYWxpemluZyBvdXIgUiBTZXNzaW9uDQoNCkxldCdzIHN0YXJ0IGJ5IHNldHRpbmcgdXAgb3VyIFIgc2Vzc2lvbi4gVGhlc2UgYXJlIGdvb2Qgc3RlcHMgdG8gdGFrZSBhdCB0aGUgc3RhcnQgb2YgYW55IFIgc2Vzc2lvbi4NCg0KMS4gU2V0IHRoZSB3b3JraW5nIGRpcmVjdG9yeTogDQpgYGB7cn0NCnNldHdkKCJDOi9Vc2Vycy9jbWl0Y2hlbGwvRG9jdW1lbnRzL1NlYUNoYW5nZVNlbWVzdGVyLzIwMjEvTGFicy9zZWEtY2hhbmdlLXNlbWVzdGVyLWRhdGEtbGFicy9MYWIwNS8iKQ0KYGBgDQoyLiBPcGVuIGEgbmV3IHNjcmlwdA0KMy4gU2F2ZSB0aGUgbmV3IHNjcmlwdCBlLmcuIGBMYWIwNS5SYA0KNC4gSW1wb3J0IHRoZSBsaWJyYXJpZXMgd2UnbGwgdXNlIHRvZGF5DQpgYGB7cn0NCmxpYnJhcnkoZ2dwbG90MikNCmxpYnJhcnkoZHBseXIpDQpsaWJyYXJ5KGx1YnJpZGF0ZSkNCmBgYA0KNS4gSW1wb3J0IG91ciBkYXRhDQpgYGB7cn0NCkRBVEEgPC0gcmVhZC5jc3YoIkRhUlRTX2NvbWJpbmVkX2RhdGEuY3N2IikNCmBgYA0KDQojIENvbnRvdXIgYW5kIEJ1YmJsZSBQbG90cw0KDQpMZXQncyBzdGFydCB3aXRoIHRoZSBmb2xsb3dpbmcgcXVlc3Rpb246DQoNCioqSG93IGRvZXMgc3VyZmFjZSBjaGxvcm9waHlsbCBmbHVvcmVzY2VuY2UgY2hhbmdlIGF0IGVhY2ggc3RhdGlvbiBvdmVyIHRoZSAyMDE2IHNhbXBsaW5nIHNlYXNvbj8qKg0KDQpTdXJmYWNlIGNhbiBiZSBkZWZpbmVkIGluIG11bHRpcGxlIHdheXMgYW5kIHRoZSBleGFjdCBjaG9pY2VzIHlvdSBtYWtlIHdpbGwgZGVwZW5kIG9uIHlvdXIgZGF0YSBhbmQgeW91ciBzaXR1YXRpb24uIEhlcmUsIHdlIGFyZSBnb2luZyB0byBzYXkgc3VyZmFjZSBjaGxvcm9waHlsbCBmbHVvcmVzY2VuY2UgaXMgdGhlIGF2ZXJhZ2UgdmFsdWUgb3ZlciB0aGUgdG9wIDEgbS4gDQoNCklmIHlvdSB3YW50IHRvIHRha2UgdGhlIHN1cmZhY2UgdmFsdWUgYXMgdGhlIGF2ZXJhZ2Ugb3ZlciB0aGUgdG9wIDIgbSAob3IgYW55IG90aGVyIGRlcHRoKSwgeW91IHdpbGwgbmVlZCB0byBnbyBiYWNrIHRvIHRoZSByYXcgQ1REIGRhdGEgYW5kIHJlZG8gdGhlIGdyb3VwaW5nIGFuZCBiaW5uaW5nIHN0ZXAgd2UgZGlkIGluIExhYiAzLiBJbiB0aGlzIGNhc2UsIHJhdGhlciB0aGFuIHJvdW5kaW5nIGFsbCB0aGUgQ1REIGRhdGEgdG8gdGhlIG5lYXJlc3QgbWV0ZXIsIHlvdSdkIGZpbHRlciBmb3IgeW91ciBzcGVjaWZpZWQgZGVwdGguIFRoZSBSIFdvcmtzaG9wIHdlIGhvc3RlZCBhdCBCaWdlbG93IGZvbGxvd3MgdGhpcyBhcHByb2FjaCwgc28gY2hlY2sgdGhhdCBvdXQgaWYgeW91IGFyZSBpbnRlcmVzdGVkLg0KDQpXZSBhcmUgZ29pbmcgdG8gY3JlYXRlIGEgY291cGxlIG9mIGRpZmZlcmVudCBmaWd1cmVzIHRoYXQgc2hvdyBzdXJmYWNlIGNobG9yb3BoeWxsIGZsdW9yZXNjZW5jZSB3aXRoIGRhdGUgYWxvbmcgdGhlIHgtYXhpcyBhbmQgc3RhdGlvbiBhbG9uZyB0aGUgeS1heGlzLiANCg0KKipBbm90aGVyIGV4YW1wbGU6KiogU2F5IHlvdSd2ZSBnb3QgdGhyZWUgY3VsdHVyZXMgdGhhdCB5b3UncmUgdGVzdGluZyBmb3VyIGRpZmZlcmVudCB0cmVhdG1lbnRzIG9uLiBGb3IgZWFjaCBjdWx0dXJlL3RyZWF0bWVudCBjb21iaW5hdGlvbiwgeW91J3JlIG1lYXN1cmluZyBjZWxsIGNvdW50cyBhdCBhIHNlcmllcyBvZiB0aW1lIHBvaW50cy4gV2hhdCB5b3UnZCBsaWtlIHRvIHZpc3VhbGl6ZS9wbG90IGlzIHRoZSBhdmVyYWdlIGNlbGwgY291bnRzIG92ZXIgdGhlIGZpcnN0IHR3byB0aW1lIHBvaW50cyBmb3IgZWFjaCBjdWx0dXJlIGFuZCB0cmVhdG1lbnQuIA0KDQpIb3cgZG8gdGhlIHZhcmlhYmxlcyBpbiB0aGVzZSBleGFtcGxlcyBjb21wYXJlPw0KDQpgYGB7ciBkYXRlX2Zvcm1hdF90YWJsZSwgZWNobyA9IEZBTFNFLCBmaWcuYWxpZ24gPSAnY2VudGVyJ30NCmtuaXRyOjprYWJsZShyZWFkcjo6cmVhZF90c3YoIkZpZWxkIERhdGEgCUxhYiBFeHBlcmltZW50DQpzdGF0aW9uIAl0cmVhdG1lbnQNCmRhdGUgCWN1bHR1cmUNCmRlcHRoIAl0aW1lIHBvaW50DQoiLCBzaG93X2NvbF90eXBlcyA9IEZBTFNFKSkNCmBgYA0KDQojIyBEYXRhIE1hbmlwdWxhdGlvbg0KDQpGaXJzdCwgd2UgYXJlIGdvaW5nIHRvIGJlIHBsb3R0aW5nIGJhc2VkIG9uIGRhdGUsIHNvIHdlIG5lZWQgdG8gY29udmVydCB0aGUgZGF0ZXMgdG8gUiBkYXRlcyAobGlrZSB3ZSBkaWQgaW4gTGFiIDMpLg0KDQpgYGB7cn0NCkRBVEEgPC0gREFUQSAlPiUgbXV0YXRlKERhdGUgPSBsdWJyaWRhdGU6OnltZChEQVRBJERhdGUpKQ0KYGBgDQoNCk91ciBkYXRhIGZyYW1lIGNvbnRhaW5zIGRhdGEgZnJvbSAyMDEyIC0gMjAyMCwgc28gd2UgbmVlZCB0byBgZmlsdGVyYCB0aGUgZGF0YSBmcmFtZSBiYXNlZCBvbiB5ZWFyIGFuZCBkZXB0aDoNCg0KYGBge3J9DQpjaGxkYXRhMjAxNiA8LSBEQVRBICU+JSBmaWx0ZXIoeWVhciA9PSAyMDE2LCBEZXB0aCA9PSAxKQ0KYGBgDQoNCg0KIyMgVmlzdWFsaXppbmcgZGF0YSB1c2luZyBhIGNvbnRvdXIgcGxvdA0KDQpXZSBjYW4gdmlzdWFsaXplIHRoaXMgdHlwZSBvZiBkYXRhIHdpdGggYSBjb250b3VyIHBsb3QgKHVzaW5nIGBnZW9tX2NvbnRvdXJfZmlsbGVkKClgKS4gV2Ugd2FudCB0byBsYWJlbCB0aGUgY29sb3IgYmFyLCBzbyBpbiB0aGlzIGNhc2UsIG91ciBhcmd1bWVudCB0byB0aGUgYGxhYnMoKWAgZnVuY3Rpb24gZm9yIHRoZSBsZWdlbmQgKGNvbG9yIGJhcikgaXMgZ29pbmcgdG8gYmUgYGZpbGxgLg0KDQpgYGB7ciwgZmlnLmhlaWdodCA9IDUsIGZpZy53aWR0aD04fQ0KZ2dwbG90KGNobGRhdGEyMDE2LGFlcyh4PURhdGUseT1TdGF0aW9uKSkgKw0KICBnZW9tX2NvbnRvdXJfZmlsbGVkKGFlcyh6PUZsdW9yZXNjZW5jZV9tZ19tMykpICsNCiAgZ2VvbV9wb2ludCgpICsNCiAgbGFicyhmaWxsPSdzdXJmYWNlIGNobG9yb3BoeWxsIGZsdW9yZXNjZW5jZSAobWcgbV4tMyknKQ0KYGBgDQoNCldlIGNhbiByZWZvcm1hdCB0aGUgeC1heGlzIGRhdGVzIGlmIHdlIHdpc2hlZCAtIHNlZSBkZXRhaWxzIGluIHRoZSBbRm9ybWF0dGluZyBEYXRlcyB3aWtpXShodHRwczovL2dpdGh1Yi5jb20vY2F0aG1taXRjaGVsbC9wbG90dGluZ09jZWFuRGF0YVdpdGhSL3dpa2kvRm9ybWF0dGluZy1kYXRlcykgdGhhdCBpcyBwYXJ0IG9mIHRoZSBSIFdvcmtzaG9wIHdlIGRpZCBhdCBCaWdlbG93LiANCg0KV2UgY2FuIGFsc28gY2hhbmdlIHRoZSBsb2NhdGlvbnMgb2YgdGhlIHgtdGlja3MuIEhvdyB3ZSBkbyB0aGlzIGRlcGVuZHMgb24gb3VyIGRhdGEgdHlwZSAtIHNlZSBzb21lIGV4YW1wbGVzIGluIHRoZSBbeC10aWNrIHdpa2ldKGh0dHBzOi8vZ2l0aHViLmNvbS9jYXRobW1pdGNoZWxsL3Bsb3R0aW5nT2NlYW5EYXRhV2l0aFIvd2lraS94LXRpY2tzLWFuZC14LXRpY2stbGFiZWxzKSAoYWxzbyBmcm9tIEJpZ2Vsb3cncyBSIFdvcmtzaG9wKS4NCg0KSW4gdGhpcyBjYXNlLCBpZiB3ZSBrbm93IHdoYXQgeC10aWNrIHBvc2l0aW9ucyB3ZSB3YW50LCB3ZSBjb3VsZCBzcGVjaWZ5IHRoZW0gaW4gYSBjYWxsIHRvIGBzY2FsZV94X2RhdGUoKWAuIFJlY2FsbCB0byBnZXQgYSBsaXN0IG9mIHRoZSBjcnVpc2UgZGF0ZXMgd2UgY2FuIHVzZSBgdW5pcXVlYDoNCg0KYGBge3J9DQp1bmlxdWUoY2hsZGF0YTIwMTYkRGF0ZSkNCmBgYA0KU28gd2UgY2FuIHVzZSB0aGlzIGluIG91ciBjYWxsIHRvIGBzY2FsZV94X2RhdGUoKWA6DQoNCmBgYHtyLCBmaWcuaGVpZ2h0ID0gNSwgZmlnLndpZHRoPTh9DQpnZ3Bsb3QoY2hsZGF0YTIwMTYsYWVzKHg9RGF0ZSx5PVN0YXRpb24pKSArDQogIGdlb21fY29udG91cl9maWxsZWQoYWVzKHo9Rmx1b3Jlc2NlbmNlX21nX20zKSkgKw0KICBnZW9tX3BvaW50KCkgKw0KICBsYWJzKGZpbGw9J3N1cmZhY2UgY2hsb3JvcGh5bGwgZmx1b3Jlc2NlbmNlIChtZyBtXi0zKScpICsNCiAgc2NhbGVfeF9kYXRlKGJyZWFrcz11bmlxdWUoY2hsZGF0YTIwMTYkRGF0ZSkpDQpgYGANCg0KSGVyZSwgdGhlIGRvdHMgKGZyb20gYGdlb21fcG9pbnRgKSBzaG93IHdoZXJlIHRoZSBtZWFzdXJlbWVudHMgd2VyZSBtYWRlLCBhbmQgdGhlIGNvbnRvdXJzIGZpbGxlZCBpbiB0aGUgZ2Fwcy4gVGhpcyBoZWxwcyB1cyB0byB2aXN1YWxpemUgdGhlIHRpbWUgYW5kIHNwYWNlIGNoYW5nZXMgaW4gY2hsb3JvcGh5bGwgZmx1b3Jlc2NlbmNlLg0KDQpJZiB3ZSB0aGluayBhYm91dCBvdXIgbGFiIGV4cGVyaW1lbnQgZXhhbXBsZSwgd2hhdCBvdXIgZmlndXJlIHdvdWxkIHNob3cgaXMgdHJlYXRtZW50IG9uIHRoZSB5LWF4aXMsIGN1bHR1cmUgb24gdGhlIHgtYXhpcywgYW5kIGNlbGwgY291bnRzIGFzIHRoZSBjb250b3Vycy4gSW4gdGhhdCBjYXNlIC0gZG9lcyBpdCBtYWtlIHNlbnNlIHRvIGhhdmUgdGhlIGNlbGwgY291bnRzIGZpbGwgdGhlIGdhcHMgYmV0d2VlbiB0aGUgdHJlYXRtZW50IGFuZCBjdWx0dXJlPyBObyAtIHRoZSB0cmVhdG1lbnRzIGFuZCBjdWx0dXJlcyBhcmUgc2VwYXJhdGUgZnJvbSBlYWNoIG90aGVyLiBUaGV5IGFyZSBub3QgY29ubmVjdGVkIGluIHRoaXMgdGVtcG9yYWwgb3Igc3BhdGlhbCB3YXkuIFNvIHdoaWxlIHRoZSAqZGF0YSBtYW5pcHVsYXRpb24qIHdhcyB0aGUgc2FtZSBmb3IgZWFjaCBkYXRhIHNldCwgdGhlICpkYXRhIHZpc3VhbGl6YXRpb24qIGlzbid0Lg0KDQojIyBWaXN1YWxpemluZyBkYXRhIHVzaW5nIGEgYnViYmxlIHBsb3QNCg0KV2UgYXJlIGdvaW5nIHRvIHBsb3QgYSBzY2F0dGVyIHBsb3QsIHdoZXJlIHRoZSBzaXplIG9mIGVhY2ggcG9pbnQgcmVwcmVzZW50cyB0aGUgY2hsb3JvcGh5bGwgZmx1b3Jlc2NlbmNlLiBJbiB0aGlzIGNhc2UsIG91ciBhcmd1bWVudCB0byB0aGUgYGxhYnMoKWAgZnVuY3Rpb24gZm9yIHRoZSBsZWdlbmQgaXMgZ29pbmcgdG8gYmUgYHNpemVgLg0KDQpgYGB7ciwgZmlnLmhlaWdodCA9IDUsIGZpZy53aWR0aD04fQ0KZ2dwbG90KGNobGRhdGEyMDE2LGFlcyh4PURhdGUseT1TdGF0aW9uKSkgKw0KICBnZW9tX3BvaW50KGFlcyhzaXplPUZsdW9yZXNjZW5jZV9tZ19tMykpICsNCiAgbGFicyhzaXplPSdzdXJmYWNlIGNobG9yb3BoeWxsIGZsdW9yZXNjZW5jZSAobWcgbV4tMyknKQ0KYGBgDQoNCiMgSW50ZXJwb2xhdGluZyBhbmQgdmlzdWFsaXppbmcgZGF0YQ0KDQpUaGUgYXBwcm9hY2ggaW4gcHJldmlvdXMgc2VjdGlvbiB3b3JrcyB3ZWxsIHdoZW4geW91ciBkYXRhIGFyZSBjb25zaXN0ZW50IGluIHRlcm1zIG9mIHRoZSB2YXJpYWJsZXMgeW91IHdhbnQgdG8gY29tcGFyZS4gRm9yIHRoZSBkYXRhIHdlIHBsb3R0ZWQgYWJvdmUsIHRoZXJlIHdlcmUgZml2ZSBkaWZmZXJlbnQgY3J1aXNlcywgYW5kIG9uIGVhY2ggY3J1aXNlLCBkYXRhIHdhcyBjb2xsZWN0ZWQgYXQgdGhlIHNhbWUgZm91ciBsb2NhdGlvbnMuIFdlIGhhZCBkYXRhIGZvciBldmVyeSBjcnVpc2UgYW5kIHN0YXRpb24uIElmIHdlJ2QgYmVlbiBtaXNzaW5nIGRhdGEgYXQgb25lIG9mIHRoZSBzdGF0aW9ucyBvbiBvbmUgb2YgdGhlIGNydWlzZXMsIHdlJ2QganVzdCBoYXZlIGEgYmxhbmsgcGFydCBvbiB0aGUgcGxvdHMgd2UgbWFkZS4gQnV0IHdoYXQgaWYgd2Ugd2VyZSBtaXNzaW5nIGEgbG90IG9mIGRhdGEgLSB3b3VsZCB0aGUgYWJvdmUgYXBwcm9hY2ggc3RpbGwgYmUgYSBnb29kIHdheSB0byB2aXN1YWxpemUgb3VyIGRhdGE/DQoNClRvIGRpZyBpbnRvIHRoaXMgYSBiaXQgZnVydGhlciwgbGV0J3MgY29uc2lkZXIgaG93IGNobG9yb3BoeWxsIGZsdW9yZXNjZW5jZSB2YXJpZXMgYnkgZGVwdGggYXQgZWFjaCBzdGF0aW9uICpmb3Igb25lIGNydWlzZSouDQoNCldlIG5lZWQgdG8gZG8gYSBiaXQgb2YgZGF0YSBtYW5pcHVsYXRpb24gYWdhaW4gdG8gY3JlYXRlIGEgZGF0YSBmcmFtZSB3ZSdsbCB1c2UgZm9yIHBsb3R0aW5nLiBJbiB0aGlzIGNhc2UsIHdlJ3JlIGdvaW5nIHRvIGhhdmUgc3RhdGlvbiBvbiB0aGUgeC1heGlzIGFuZCBkZXB0aCBvbiB0aGUgeS1heGlzLCBhbmQgd2UnbGwgY29uc2lkZXIgdGhlIGNydWlzZSB0aGF0IHRvb2sgcGxhY2Ugb24gU2VwdGVtYmVyIDh0aCAyMDE2Lg0KDQpgYGB7cn0NCmNydWlzZURhdGEgPC0gZmlsdGVyKERBVEEsIERhdGU9PSIyMDE2LTA5LTA4IikNCmBgYA0KDQpXZSBub3cgaGF2ZSBhIGRhdGEgZnJhbWUgdGhhdCBpbmNsdWRlcyBkYXRhIGZvciBqdXN0IG9uZSBjcnVpc2UgLSBzbyBsZXQncyB1c2UgdGhlIHNhbWUgYXBwcm9hY2ggYXMgYmVmb3JlIHRvIGNyZWF0ZSBhIGNvbnRvdXIgcGxvdDoNCg0KYGBge3IsIGZpZy5oZWlnaHQgPSA1LCBmaWcud2lkdGg9OH0NCmdncGxvdChjcnVpc2VEYXRhLGFlcyh4PVN0YXRpb24seT1EZXB0aCkpICsNCiAgZ2VvbV9jb250b3VyX2ZpbGxlZChhZXMoej1GbHVvcmVzY2VuY2VfbWdfbTMpKSArDQogIGdlb21fcG9pbnQoKSArDQogIGxhYnMoZmlsbD0nY2hsb3JvcGh5bGwgZmx1b3Jlc2NlbmNlIChtZyBtXi0zKScpICsNCiAgc2NhbGVfeV9yZXZlcnNlKCkgI2ZsaXAgeS1heGlzDQpgYGANCg0KVGhpcyBsb29rcyBnb29kLCBidXQgd2hhdCB0aGlzIHBsb3R0aW5nIGZ1bmN0aW9uIGRvZXNuJ3QgZG8gaXMgaW50ZXJwb2xhdGUgZGF0YSBiZXR3ZWVuIG1pc3NpbmcgZGF0YSBwb2ludHMuIFdlIGtub3cgd2UgaGF2ZSBkYXRhIGF0IHN0YXRpb24gNCBiZWxvdyA1MCBtIHRoYXQgaXNuJ3QgcmVwcmVzZW50ZWQgaW4gdGhpcyBwbG90LiBDYW4gd2UgdXNlIGEgZGlmZmVyZW50IGZ1bmN0aW9uIHRvIHNob3cgdGhvc2UgZGF0YSB0b28/IFllcyAtIGBnZW9tX3RpbGUoKWAuDQoNCmBgYHtyLCBmaWcuaGVpZ2h0ID0gNSwgZmlnLndpZHRoPTh9DQpnZ3Bsb3QoY3J1aXNlRGF0YSxhZXMoeD1TdGF0aW9uLHk9RGVwdGgpKSArDQogIGdlb21fdGlsZShhZXMoZmlsbD1GbHVvcmVzY2VuY2VfbWdfbTMpKSArDQogIGxhYnMoZmlsbD0nY2hsb3JvcGh5bGwgZmx1b3Jlc2NlbmNlIChtZyBtXi0zKScpICsNCiAgc2NhbGVfeV9yZXZlcnNlKCkNCmBgYA0KDQpMZXQncyBnbyBiYWNrIHRvIHRoZSBsYWIgZXhwZXJpbWVudCBleGFtcGxlOiBjb3VsZCBpdCBiZSBwbG90dGVkIGluIHRoaXMgd2F5PyANCg0KWWVzIC0gZGVwdGggY291bGQgcmVwcmVzZW50IHRpbWUgcG9pbnRzLCBhbmQgc3RhdGlvbiBjb3VsZCByZXByZXNlbnQgdHJlYXRtZW50Lg0KDQojIyBJbnRlcnBvbGF0aW9uDQoNCldpdGggb3VyIGZpZWxkIGRhdGEgb25lIGF4aXMgaGFzIGJlZW4gc3RhdGlvbiBudW1iZXIgaS5lLiBpdCBpcyBhIGdpdmVuIGxvY2F0aW9uLiBXZSBtaWdodCB3YW50IHRvIHNwYWNlIG91dCB0aGUgZGF0YSBvbiB0aGUgeC1heGlzIGJhc2VkIG9uIHRob3NlIGxvY2F0aW9ucywgcmF0aGVyIHRoYW4gc3RhdGlvbiBudW1iZXIsIHNvIHdlIGNhbiB2aXN1YWxpemUgb3VyIGRhdGEgd2l0aCBhIHJlcHJlc2VudGF0aXZlIHNlcGFyYXRpb24gYmV0d2VlbiB0aGUgZGF0YSBwb2ludHMuIEZvciB0aGlzIGRhdGEgc2V0LCB0aGUgc3RhdGlvbnMgYXJlIG9uIGEgc2ltaWxhciBsb25naXR1ZGUsIHNvIGxvb2tpbmcgYXQgbGF0aXR1ZGUgd2lsbCBnaXZlIGEgZ29vZCByZXByZXNlbnRhdGlvbiBvZiB0aGUgc2VwYXJhdGlvbiBiZXR3ZWVuIHRoZSBzdGF0aW9ucy4gRm9yIGNydWlzZSBvciBzYW1wbGluZyB0cmFja3Mgd2hlcmUgYm90aCBsYXRpdHVkZSBhbmQgbG9uZ2l0dWRlIGFyZSB2YXJpYWJsZSwgdGhpcyBjb3VsZCBiZSBkaXN0YW5jZSBhbG9uZyB0aGUgY3J1aXNlIHBhdGggb3IgZmllbGQgdHJhbnNlY3QuDQoNCk91ciBkYXRhZnJhbWUgZG9lc24ndCBoYXZlIHRoZSBsYXRpdHVkZSBpbiBpdCwgb25seSB0aGUgc3RhdGlvbiBudW1iZXJzLiBTbyB3ZSBuZWVkIHRvIGZpcnN0IHJlZG8gaW5jbHVkZSBsYXRpdHVkZSBpbiBvdXIgZGF0YSBmcmFtZS4gVG8gZG8gdGhpcyB3ZSBhcmUgZ29pbmcgdG8gdXNlICoqY29uZGl0aW9uYWwgc3RhdGVtZW50cyoqLg0KDQojIyMgQ29kaW5nIHRlY2huaXF1ZTogQ29uZGl0aW9uYWwgU3RhdGVtZW50cw0KDQpBIGNvbmRpdGlvbmFsIHN0YXRlbWVudCBpcyBvbmUgdGhhdCB0ZWxscyB5b3UgdG8gZG8gc29tZXRoaW5nIGJhc2VkIG9uIGEgY29uZGl0aW9uIGUuZy4gaWYgdGhlcmUgaXMgY2FrZSBpbiB0aGUgc3VwZXJtYXJrZXQsIHRoZW4gYnV5IDEuIFNvbWV0aW1lcywgdGhlcmUgbWlnaHQgYmUgYSBkaWZmZXJlbnQgYWN0aW9uIHRvIHRha2UgaWYgdGhlIGNvbmRpdGlvbiBpcyBmYWxzZSBlLmcuIGlmIHRoZXJlIGlzIGNha2UgaW4gdGhlIHN1cGVybWFya2V0LCB0aGVuIGJ1eSAxLCBlbHNlIGJ1eSAxIGxvYWYgb2YgYnJlYWQuDQoNCkhlcmUsIHRoZSBpbXBvcnRhbnQgd29yZHMgYXJlICoqaWYqKiwgKip0aGVuKiogYW5kICoqZWxzZSoqLiBUb2dldGhlciwgdGhlc2UgbWFrZSB1cCBzb21ldGhpbmcgY2FsbGVkIGFuICoqaWYtZWxzZSoqIHN0YXRlbWVudC4gU29tZXRpbWVzIGl0IGVhc2llciB0byB2aXN1YWxpemUgdGhlc2UgYXMgZmxvdyBjaGFydHM6DQoNCjxjZW50ZXI+PGltZyBzcmM9ImZpZ3VyZXMvYnJlYWQtaWZlbHNlLmpwZyIgd2lkdGg9NzAlPjwvY2VudGVyPg0KDQpJZiB3ZSBkaWRuJ3QgaGF2ZSB0aGUgYGVsc2VgIHN0YXRlbWVudCwgdGhlbiwgdGhlIGRlZmF1bHQgaXMgdG8gZG8gbm90aGluZyBpZiB0aGUgZmlyc3QgY29uZGl0aW9uIGlzIGZhbHNlLiANCg0KVGhlcmUncyBhIGNvdXBsZSBvZiBkaWZmZXJlbnQgd2F5cyB0byBjb2RlIGBpZmAgYW5kIGBpZi1lbHNlYCBzdGF0ZW1lbnRzIGluIFIuIFRvIGNvZGUgdGhpcyBpbiBSLCB3ZSB3b3VsZCBkbyB0aGUgZm9sbG93aW5nOg0KDQpgYGANCmlmKGNvbmRpdGlvbil7DQogIGNvZGUgaWYgVFJVRQ0KfSBlbHNlIHsNCiAgY29kZSBpZiBGQUxTRQ0KfQ0KYGBgDQoNCkJ1dCB3ZSBjb3VsZCBhbHNvIHdyaXRlIHRoaXMgYXMgdGhlIGZvbGxvd2luZzoNCg0KYGBgIA0KaWZlbHNlKGNvbmRpdGlvbiwgY29kZSBpZiB0cnVlLCBjb2RlIGlmIGZhbHNlKQ0KYGBgDQoNCkhvdyBjYW4gd2UgdXNlIHRoaXMgaWRlYSBmb3IgaW5jbHVkaW5nIHRoZSBsYXRpdHVkZXMgaW50byBvdXIgZGF0YSBmcmFtZT8gV2UgY2FuIHRoaW5rIG9mIHRoaXMgcHJvY2VzcyBhczogDQoNCiogaWYgc3RhdGlvbiA9IDEsIHRoZW4gbGF0aXR1ZGUgPSA0My45MDMNCiogaWYgc3RhdGlvbiA9IDIsIHRoZW4gbGF0aXR1ZGUgPSA0My44NjMNCiogaWYgc3RhdGlvbiA9IDMsIHRoZW4gbGF0aXR1ZGUgPSA0My44MTENCiogaWYgc3RhdGlvbiA9IDQsIHRoZW4gbGF0aXR1ZGUgPSA0My43NTMuIA0KDQpUaGF0J3MgYSBsb3Qgb2Ygc3RhdGVtZW50cy4gTGV0J3MgdHJ5IGFuZCB2aXN1YWxpemUgdGhpczoNCg0KPGNlbnRlcj48aW1nIHNyYz0iZmlndXJlcy9zdGF0aW9uLWlmZWxzZS1zdGFydC5qcGciIHdpZHRoPTcwJT48L2NlbnRlcj4NCg0KV2hhdCBkbyB3ZSBkbyBpZiBvdXIgdGVzdCBmb3Igc3RhdGlvbiAxIGlzIGZhbHNlIChpLmUuIGlmIHdlIGFyZSBhdCBzdGF0aW9uIDIsIDMgb3IgNCk/IFdlIGNvdWxkIHRlc3QgaWYgaXQgaXMgc3RhdGlvbiAyLiBUaGVuLCBpZiBpdCBpcywgYXNzaWduIHRoZSBzdGF0aW9uIDIgbGF0aXR1ZGUgdmFsdWUsIGVsc2UgdGVzdCBpZiBpdCBpcyBzdGF0aW9uIDMuIFRoZW4sIGlmIGl0IGlzIHN0YXRpb24gMywgd2UgY291bGQgYXNzaWduIHRoZSBzdGF0aW9uIDMgbGF0aXR1ZGUgdmFsdWUsIGVsc2UgdGVzdCBpZiBpdCBpcyBzdGF0aW9uIDQuIFRoZW4sIGlmIGl0IGlzIHN0YXRpb24gNCwgd2UgY291bGQgYXNzaWduIHRoZSBzdGF0aW9uIDQgbGF0aXR1ZGUgdmFsdWUuIFdoYXQgZG8gd2UgZG8gaWYgdGhlIGZpbmFsIHRlc3QgKGkuZS4gc3RhdGlvbiA0KSBpcyBmYWxzZT8gVGhhdCBtZWFucyBpdCBpc24ndCBBTlkgb2YgdGhlIHN0YXRpb25zLCBzbyB0aGVyZSdzIHByb2JhYmx5IGFuIGVycm9yIHNvbWV3aGVyZSBpbiBvdXIgZGF0YXNldCwgYW5kIHdlIHNob3VsZCBzZXQgdGhhdCBsYXRpdHVkZSB0byBOb3QtQS1OdW1iZXIgKE5hTikuIA0KDQpMZXQncyBmaXJzdCBsb29rIGF0IHRoaXMgdmlzdWFsbHk6DQoNCjxjZW50ZXI+PGltZyBzcmM9ImZpZ3VyZXMvc3RhdGlvbi1pZmVsc2UuanBnIiB3aWR0aD03MCU+PC9jZW50ZXI+DQoNCkhvdyBkbyB3ZSBjb21iaW5lIGFsbCB0aGVzZSBzdGF0ZW1lbnRzIGludG8gc29tZSBSIGNvZGU/IFdlIGNhbiB1c2UgdGhlIGBpZmVsc2VgIGZ1bmN0aW9uLiBSZWNhbGwgaXQgaGFzIHRoZSBmb3JtOg0KDQpgYGAgDQppZmVsc2UoY29uZGl0aW9uLCBjb2RlIGlmIHRydWUsIGNvZGUgaWYgZmFsc2UpDQpgYGANCg0KSW4gb3VyIGNhc2UsIHdlIGhhdmUgdG8gaW5jbHVkZSBhbiBgaWZlbHNlYCBzdGF0ZW1lbnQgYXMgdGhlIGBjb2RlIGlmIGZhbHNlYCBwYXJ0Og0KDQpgYGANCmlmZWxzZShzdGF0aW9uMXRlc3QsIGxhdGl0dWRlID0gdmFsdWUxLCBpZmVsc2Uoc3RhdGlvbjJ0ZXN0LCBsYXRpdHVkZSA9IHZhbHVlMiwgY29kZSBpZiBmYWxzZSkpDQpgYGANCkFuZCBrZWVwIGRvaW5nIHRoaXMgZm9yIGFsbCB0aGUgc3RhdGlvbnM6DQoNCmBgYA0KaWZlbHNlKHN0YXRpb24xdGVzdCwgbGF0aXR1ZGUgPSB2YWx1ZTEsIA0KaWZlbHNlKHN0YXRpb24ydGVzdCwgbGF0aXR1ZGUgPSB2YWx1ZTIsIA0KaWZlbHNlKHN0YXRpb24zdGVzdCwgbGF0aXR1ZGUgPSB2YWx1ZTMsDQppZmVsc2Uoc3RhdGlvbjR0ZXN0LCBsYXRpdHVkZSA9IHZhbHVlNCxOYU4pKSkpDQpgYGANCg0KTGV0J3MganVzdCBzcGxpdCB0aGlzIHVwIHRvIHNlZSB3aGF0J3MgZ29pbmcgb24gbW9yZSBjbGVhcmx5LiBXZSdsbCBzdGFydCB3aXRoIHRoZSBsYXN0IGBpZmVsc2VgOg0KDQo8Y2VudGVyPjxpbWcgc3JjPSJmaWd1cmVzLzR0aC1pZmVsc2UtcmV2LnBuZyIgd2lkdGg9NzAlPjwvY2VudGVyPg0KDQpJbiB0aGUgYWJvdmUgaW1hZ2UsIHRoZSBwYXJ0IGluIHRoZSBzb2xpZCBncmV5IGJveCBpcyB0aGUgYGNvZGUgaWYgZmFsc2VgIHBhcnQgZm9yIHRoZSBsYXN0IGBpZmVsc2VgIHN0YXRlbWVudC4gRm9yIHRoZSAzcmQgYGlmZWxzZWA6DQoNCjxjZW50ZXI+PGltZyBzcmM9ImZpZ3VyZXMvM3JkLWlmZWxzZS1yZXYucG5nIiB3aWR0aD03MCU+PC9jZW50ZXI+DQoNClRoZSBwYXJ0IGluIHRoZSBkYXNoZWQgZ3JlZW4gYm94IGlzIHRoZSBgY29kZSBpZiBmYWxzZWAgcGFydCBmb3IgdGhlIDNyZCBgaWZlbHNlYCBzdGF0ZW1lbnQuIEZvciB0aGUgMm5kIGBpZmVsc2VgOg0KDQo8Y2VudGVyPjxpbWcgc3JjPSJmaWd1cmVzLzJuZC1pZmVsc2UtcmV2LnBuZyIgd2lkdGg9NzAlPjwvY2VudGVyPg0KDQpUaGUgcGFydCBpbiB0aGUgZG90dGVkIG9yYW5nZSBib3ggaXMgdGhlIGBjb2RlIGlmIGZhbHNlYCBwYXJ0IGZvciB0aGUgMm5kIGBpZmVsc2VgIHN0YXRlbWVudC4gRm9yIHRoZSAxc3QgYGlmZWxzZWA6DQoNCjxjZW50ZXI+PGltZyBzcmM9ImZpZ3VyZXMvMXN0LWlmZWxzZS1yZXYucG5nIiB3aWR0aD03MCU+PC9jZW50ZXI+DQoNClRoZSBwYXJ0IGluIHRoZSBzb2xpZCBibHVlIGJveCBpcyB0aGUgYGNvZGUgaWYgZmFsc2VgIHBhcnQgZm9yIHRoZSAxc3QgYGlmZWxzZWAuDQoNCkFuZCBmaW5hbGx5LCBwdXR0aW5nIHRoaXMgYWxsIHRvZ2V0aGVyOg0KDQpgYGB7cn0NCmxhdGl0dWRlID0gaWZlbHNlKGNydWlzZURhdGEkU3RhdGlvbiA9PSAxLCA0My45MDMsaWZlbHNlKGNydWlzZURhdGEkU3RhdGlvbiA9PSAyLCA0My44NjMsaWZlbHNlKGNydWlzZURhdGEkU3RhdGlvbiA9PSAzLCA0My44MTEsaWZlbHNlKGNydWlzZURhdGEkU3RhdGlvbiA9PSA0LDQzLjc1MyxOYU4pKSkpDQoNCmBgYA0KDQpOb3cgbGV0J3MgYWRkIHRoaXMgY29sdW1uIG9mIGxhdGl0dWRlcyB0byBvdXIgZGF0YWZyYW1lIGFuZCBtYWtlIG91ciBwbG90Og0KDQpgYGB7ciwgZmlnLmhlaWdodCA9IDUsIGZpZy53aWR0aD04fQ0KY3J1aXNlRGF0YSA8LSBjcnVpc2VEYXRhICU+JSBtdXRhdGUobGF0aXR1ZGUgPSBsYXRpdHVkZSkNCg0KZ2dwbG90KGNydWlzZURhdGEsYWVzKHg9bGF0aXR1ZGUseT1EZXB0aCkpICsNCiAgZ2VvbV90aWxlKGFlcyhmaWxsPUZsdW9yZXNjZW5jZV9tZ19tMykpICsNCiAgbGFicyhmaWxsPSdjaGxvcm9waHlsbCBmbHVvcmVzY2VuY2UgKG1nIG1eLTMpJykgKw0KICBzY2FsZV95X3JldmVyc2UoKSArDQogIHNjYWxlX3hfcmV2ZXJzZSgpICMgZmxpcHBpbmcgc28gc3RhdGlvbiAxIGlzIG9uIHRoZSBsZWZ0DQpgYGANCg0KV2hhdCB3ZSBlbmQgdXAgd2l0aCBpcyB0aGlzIHBsb3Qgd2hlcmUgd2UgaGF2ZSBnYXBzIGJldHdlZW4gZWFjaCBzdGF0aW9uIG1lYXN1cmVtZW50IGJlY2F1c2Ugb3VyIHNhbXBsaW5nIHN0YXRpb25zIGFyZSBub3QgZXF1YWxseSBzcGFjZWQgaW4gdGVybXMgb2YgbGF0aXR1ZGUuIFdoYXQgd2UgY2FuIGRvIGlzIGVzdGltYXRlIHdoYXQgdGhlIGNobG9yb3BoeWxsIGZsdW9yZXNjZW5jZSB3b3VsZCBiZSBpbiBiZXR3ZWVuIHdoZXJlIHdlIGhhdmUgb3VyIHNhbXBsZXMgaS5lLiB3ZSBjYW4gaW50ZXJwb2xhdGUgb3VyIGRhdGEuDQoNCldlIGFyZSBnb2luZyB0byBkbyB0aGUgYmlsaW5lYXIgaW50ZXJwb2xhdGlvbiBvbiBvdXIgZGF0YSwgd2hpY2ggbWVhbnMgd2UgYXJlIGdvaW5nIHRvIGVzdGltYXRlIGNobG9yb3BoeWxsIGZsdW9yZXNjZW5jZSB2YWx1ZXMgb3ZlciByZWd1bGFybHkgc3BhY2VkIGxhdGl0dWRlIGFuZCBkZXB0aCB2YWx1ZXMuIFRoaXMgaXMgc29tZXRoaW5nIHRoYXQncyBtYXliZSBtb3JlIGNvbW1vbiB3aGVuIHdvcmtpbmcgd2l0aCBnZW9zcGF0aWFsIGRhdGEuIEJ1dCBpbiBvdXIgbGFiIGV4cGVyaW1lbnQgY2FzZSwgbWF5YmUgd2UnZCBiZSBpbnRlcmVzdGVkIGluIGludGVycG9sYXRpbmcgb3VyIGNlbGwgY291bnRzIGJldHdlZW4gb3VyIGRpZmZlcmVudCB0aW1lIHBvaW50cy4gDQoNCldlJ3JlIGdvaW5nIHRvIHVzZSBhIGhhbmR5IGZ1bmN0aW9uIGZyb20gdGhlIGBha2ltYWAgcGFja2FnZSB0byBkbyBhbGwgdGhlIGhhcmQgd29yayBmb3IgdXMuIEJ1dCBmaXJzdCwgd2UgbmVlZCB0byBtYWtlIHN1cmUgd2UgaGF2ZSBubyBuYW5zIGluIG91ciBkYXRhLiBSZWNhbGwgd2UgZGlkIHRoaXMgbGFzdCBkYXkuDQoNCmBgYHtyfQ0KIyByZW1vdmluZyB0aGUgTmFOcyBhbmQgSW5mczoNCiMgMSkgbWFrZSBhIGJvb2xlYW4gbGlzdCBvZiB0aGUgZ29vZCB2YWx1ZXMNCm5vbmFucyA8LSBpcy5maW5pdGUoY3J1aXNlRGF0YSRGbHVvcmVzY2VuY2VfbWdfbTMpDQojIDIpIGluZGV4IGZsdW9yZXNjZW5jZSwgbGF0aXR1ZGUgYW5kIGRlcHRoIGNvbHVtbnMgdXNpbmcgb3VyIGJvb2xlYW4gbGlzdA0KbGF0bm9uYW4gPC0gY3J1aXNlRGF0YSRsYXRpdHVkZVtub25hbnNdDQpmbG5vbmFuIDwtIGNydWlzZURhdGEkRmx1b3Jlc2NlbmNlX21nX20zW25vbmFuc10NCmRlcHRobm9uYW4gPC0gY3J1aXNlRGF0YSREZXB0aFtub25hbnNdDQpgYGANCg0KTmV4dCB3ZSBjYW4gZG8gdGhlIGludGVycG9sYXRpb24NCg0KYGBge3J9DQpsaWJyYXJ5KGFraW1hKQ0KDQojaW50ZXJwb2xhdGUgb3VyIGRhdGE6DQppbnRlcnBSZWZlcmVuY2UgPC0gaW50ZXJwKGxhdG5vbmFuLCBkZXB0aG5vbmFuLCBmbG5vbmFuKQ0KYGBgDQoNCldoYXQgZG9lcyBgaW50ZXJwUmVmZXJlbmNlYCBsb29rIGxpa2U/IEl0IGlzIGEgbGlzdCB3aXRoIDMgaXRlbXM6IA0KDQogMS4gYHhgLCB3aGljaCBpcyBhIGxpc3Qgb2YgNDAsIHJlZ3VsYXJseSBzcGFjZWQgbGF0aXR1ZGVzDQogMi4gYHlgLCB3aGljaCBpcyBhIGxpc3Qgb2YgNDAsIHJlZ3VsYXJseSBzcGFjZWQgZGVwdGhzDQogMy4gYHpgLCB3aGljaCBpcyBhIDQwIHggNDAgZ3JpZCAob3IgKiptYXRyaXgqKiksIHdoZXJlIGVhY2ggKiplbGVtZW50KiogKG51bWJlciBpbiB0aGUgZ3JpZCkgY29ycmVzcG9uZHMgdG8gb25lIG9mIHRoZSBkZXB0aCAoY29sdW1ucykgYW5kIGxhdGl0dWRlIChyb3dzKSB2YWx1ZXMuIEZvciBleGFtcGxlLCANCiAgICBhLiB0aGUgYHpgIHZhbHVlIGluIHRoZSBmaXJzdCByb3cgYW5kIGZpcnN0IGNvbHVtbiBjb3JyZXNwb25kcyB0byB0aGUgZmlyc3QgbGF0aXR1ZGUgYW5kIGRlcHRoIHZhbHVlcw0KICAgIGIuIHRoZSBgemAgdmFsdWUgaW4gdGhlIGZpcnN0IHJvdyBhbmQgc2Vjb25kIGNvbHVtbiBjb3JyZXNwb25kcyB0byB0aGUgZmlyc3QgbGF0aXR1ZGUsIGFuZCBzZWNvbmQgZGVwdGggdmFsdWUNCiAgICBjLiB0aGUgYHpgIHZhbHVlIGluIHRoZSBzZWNvbmQgcm93IGFuZCBmaXJzdCBjb2x1bW4gY29ycmVzcG9uZHMgdG8gdGhlIHNlY29uZCBsYXRpdHVkZSwgYW5kIGZpcnN0IGRlcHRoIHZhbHVlDQoNCkhlcmUncyBhbiBleGFtcGxlIGZvciBhIHNtYWxsZXIgbWF0cml4Og0KDQo8Y2VudGVyPjxpbWcgc3JjPSJmaWd1cmVzL3otbWF0cml4LmpwZyIgd2lkdGg9NzAlPjwvY2VudGVyPg0KDQpXaGF0IHdlIG5vdyBuZWVkIHRvIGRvIGlzIGdldCBvdXIgZGF0YSBpbnRvIGEgZGF0YSBmcmFtZSBmb3JtYXQgZm9yIGBnZ3Bsb3RgLiBPbmUgd2F5IHRvIGRvIHRoaXMgaXMgdXNlIHRoZSBgZXhwYW5kLmdyaWRgIGZ1bmN0aW9uIHRvIGNyZWF0ZSBhIGRhdGEgZnJhbWUgZnJvbSBhbGwgY29tYmluYXRpb25zIG9mIHRoZSBsYXRpdHVkZSBhbmQgZGVwdGggdmVjdG9ycy4gVGhlbiBmbGF0dGVuIHRoZSBpbnRlcnBvbGF0ZWQgZGF0YSBtYXRyaXggaW50byBhIHZlY3RvciBhbmQgYWRkIGl0IGFzIGEgY29sdW1uIGluIHRoZSBkYXRhIGZyYW1lLg0KDQpgYGB7cn0NCiMgbWFraW5nIGRhdGEgZnJhbWUgZnJvbSBhbGwgY29tYmluYXRpb25zIG9mIGxhdGl0dWRlIGFuZCBkZXB0aA0KQ3J1aXNlRGF0YUludGVycCA8LSBleHBhbmQuZ3JpZChsYXRpdHVkZT1pbnRlcnBSZWZlcmVuY2UkeCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGVwdGggPSBpbnRlcnBSZWZlcmVuY2UkeSkgJT4lDQogIG11dGF0ZShmbHVvcmVzY2VuY2UgPSBhcy52ZWN0b3IoaW50ZXJwUmVmZXJlbmNlJHopKQ0KYGBgDQoNCipOb3RlOiBTb21ldGhpbmcgdG8gYmUgYXdhcmUgb2Ygd2l0aCB0aGUgYWJvdmUgc3RlcCBpcyBpZiB0aGUgbWF0cml4IGlzIGZsYXR0ZW5lZCByb3ctd2lzZSBvciBjb2x1bW4td2lzZS4gVG8gbWFrZSBzdXJlIHRoZSBmbGF0dGVuZWQgbWF0cml4IGVsZW1lbnRzIGFsaWduIHdpdGggdGhlIGNvcnJlY3QgbGF0aXR1ZGUgYW5kIGRlcHRoIHBhaXIsIHlvdSBtaWdodCBuZWVkIHRvIHRyYW5zcG9zZSB5b3VyIG1hdHJpeCBmaXJzdCAoZmxpcCBpdCBhbG9uZyB0aGUgZGlhZ29uYWwpIGB0KGludGVycFJlZmVyZW5jZSR6KWAqDQoNCk5vdyB3ZSBoYXZlIG91ciBkYXRhIGZyYW1lIC0gY2FuIHdlIHBsb3Qgb3VyIGRhdGEgYXMgYmVmb3JlPw0KDQpgYGB7cn0NCmdncGxvdChDcnVpc2VEYXRhSW50ZXJwLCBhZXMoeD1sYXRpdHVkZSwgeT1kZXB0aCkpICsNCiAgZ2VvbV90aWxlKGFlcyhmaWxsID0gZmx1b3Jlc2NlbmNlKSkgKw0KICBzY2FsZV95X3JldmVyc2UoKSArDQogIHNjYWxlX3hfcmV2ZXJzZSgpICsgI3NvIHN0YXRpb24xIGlzIG9uIHRoZSBsZWZ0IGFuZCBzdGF0aW9uNCBpcyBvbiB0aGUgcmlnaHQNCiAgbGFicyh5PSJEZXB0aCAobSkiLCBmaWxsPSJmbHVvcmVzY2VuY2UgKG1nIG1eLTMpIikgKw0KICBzY2FsZV9maWxsX2Rpc3RpbGxlcihwYWxldHRlPSJHcmVlbnMiLGRpcmVjdGlvbj0xKQ0KYGBgDQoNCldlIGNhbiBpbmNsdWRlIHRoZSBzYW1wbGUgbG9jYXRpb25zIGFuZCBjb250b3VyIGxpbmVzIHRvIG1ha2UgaXQgY2xlYXIgd2hlcmUgdGhlIGludGVycG9sYXRpb24gaXMgaGFwcGVuaW5nOg0KDQpgYGB7cn0NCmdncGxvdChDcnVpc2VEYXRhSW50ZXJwLCBhZXMoeD1sYXRpdHVkZSwgeT1kZXB0aCkpICsNCiAgZ2VvbV90aWxlKGFlcyhmaWxsID0gZmx1b3Jlc2NlbmNlKSkgKw0KICBnZW9tX2NvbnRvdXIoYWVzKHogPSBmbHVvcmVzY2VuY2UpLGNvbG9yPSJ3aGl0ZSIpICsNCiAgZ2VvbV9wb2ludChkYXRhID0gY3J1aXNlRGF0YSwgYWVzKHg9bGF0aXR1ZGUsIHk9RGVwdGgpLGNvbG9yPSJibGFjayIpICsgI2FkZGluZyBpbiB0aGUgbWVhc3VyZW1lbnQgbG9jYXRpb25zDQogIHNjYWxlX3lfcmV2ZXJzZSgpICsNCiAgc2NhbGVfeF9yZXZlcnNlKCkgKyAjc28gc3RhdGlvbjEgaXMgb24gdGhlIGxlZnQgYW5kIHN0YXRpb240IGlzIG9uIHRoZSByaWdodA0KICBsYWJzKHk9IkRlcHRoIChtKSIsIGZpbGw9ImZsdW9yZXNjZW5jZSAobWcgbV4tMykiKSArDQogIHNjYWxlX2ZpbGxfZGlzdGlsbGVyKHBhbGV0dGU9IkdyZWVucyIsZGlyZWN0aW9uPTEpDQpgYGANCg0KKk5vdGU6IEluIHRoaXMgY2FzZSAodGhlIERhbWFyaXNjb3R0YSBSaXZlcikgd2UgaW50ZXJwb2xhdGVkIGJlbG93IHRoZSBtZWFzdXJlZCBkZXB0aHMuIEJ1dCB0aG9zZSBpbnRlcnBvbGF0aW9ucyBkb24ndCBtYWtlIHNlbnNlIGhlcmUgYmVjYXVzZSB0aGUgcHJvZmlsZXMgYXJlIGZ1bGwgd2F0ZXIgY29sdW1uIGkuZS4gZGVwdGggd2FzIGxpbWl0ZWQgYnkgYmF0aHltZXRlcnkuKg0KDQpJbiB0aGlzIGV4YW1wbGUsIHdlJ3ZlIGludGVycG9sYXRlZCBvdXIgZGF0YSBvdmVyIGRlcHRoIGFuZCBsYXRpdHVkZS4gQnV0LCB3ZSBjb3VsZCBoYXZlIGZvY3VzZWQgb24gb25lIHN0YXRpb24gYW5kIGRvbmUgZGVwdGggYnkgZGF0ZSBpbnN0ZWFkLiBUaGVzZSB0ZWNobmlxdWVzIHdlJ3ZlIGJlZW4gbGVhcm5pbmcgYXJlIG5vdCBvbmx5IGZvciB0aGlzIGRhdGFzZXQgYW5kIHRoZXNlIHNwZWNpZmljIHBhcmFtZXRlcnMuIFdoYXQgd2UndmUgYmVlbiBsZWFybmluZyBhcmUgc3BlY2lmaWMgc2tpbGxzIGFuZCB0ZWNobmlxdWVzIHRoYXQgY2FuIGJlIGFwcGxpZWQgdG8gYW55IHR5cGUgb2YgZGF0YS4NCg0KIyBOZXh0IHN0ZXBzOiBGdW5jdGlvbnMgYW5kIEZvciBMb29wcw0KDQpUaGUgbGFzdCBmaWd1cmUgd2UgY3JlYXRlZCB3YXMgZm9yIG9uZSB2YXJpYWJsZSwgZm9yIG9uZSBjcnVpc2UuIFdoYXQgaWYgd2Ugd2FudCB0byBsb29rIGF0IGEgZGlmZmVyZW50IHZhcmlhYmxlPyBPciBhIGRpZmZlcmVudCBjcnVpc2U/IEl0IHdvdWxkIGJlIG5pY2UgdG8gaGF2ZSBhIGZ1bmN0aW9uIHRvIHJlZ2VuZXJhdGUgYSBwbG90IHF1aWNrbHkuDQoNCllvdSBjb3VsZCB3cml0ZSBhIGZ1bmN0aW9uIHRoYXQgY3JlYXRlcyB0aGUgYWJvdmUgZmlndXJlLg0KDQpGb3IgbG9vcHMgYXJlIGEgd2F5IHdlIGNhbiByZXBlYXQgYSBzcGVjaWZpYyB0YXNrIGEgc2V0IG51bWJlciBvZiB0aW1lcy4gRm9yIGV4YW1wbGUsIHlvdSBjb3VsZCBjcmVhdGUgcGxvdHMgZm9yIGV2ZXJ5IGNydWlzZSBpbiAyMDE2Lg0KDQpXZSBkb24ndCBoYXZlIHRpbWUgdG8gZ28gb3ZlciBmdW5jdGlvbnMgYW5kIGZvciBsb29wcywgYnV0IHNlZSBbdGhlIEJpZ2Vsb3cgUiBXb3Jrc2hvcCB0dXRvcmlhbF0oaHR0cHM6Ly9naXRodWIuY29tL2NhdGhtbWl0Y2hlbGwvcGxvdHRpbmdPY2VhbkRhdGFXaXRoUi9ibG9iL21haW4vcGxvdHRpbmdPY2VhbkRhdGFXaXRoUi5tZCNmdW5jdGlvbnMtYW5kLWZvci1sb29wcy1mb3ItY3JlYXRpbmctbXVsdGlwbGUtdmVyc2lvbnMtb2YtZmlndXJlcykgZm9yIGEgZGV0YWlsZWQgd2Fsay10aHJvdWdoLg0KDQojIFNlYSBDaGFuZ2UgU2VtZXN0ZXIgRGF0YSBMYWJzIEdpdEh1Yg0KDQpBbGwgdGhlIGxhYnMgZnJvbSBDb3Vyc2UgMSBhcmUgb24gW0JpZ2Vsb3cncyBHaXRIdWJdKGh0dHBzOi8vZ2l0aHViLmNvbS9CaWdlbG93TGFiL3NlYS1jaGFuZ2Utc2VtZXN0ZXItZGF0YS1sYWJzKSBzbyB5b3UgY2FuIGFjY2VzcyB0aGVtIHRoZXJlIGFueSB0aW1lLg0KDQpOb3RlIGlmIHlvdSB3YW50IHRvIHZpZXcgdGhlIGxhYiBzY3JpcHRzIG9uIEdpdEh1YiwgY2xpY2sgb24gdGhlIGAubWRgIGZpbGUgd2l0aGluIGVhY2ggbGFiIGZvbGRlciAgKCpub3QgdGhlIGAuUm1kYCBPUiBgLmh0bWxgIGZpbGUqKS4NCg==